ویژگیهای پیشرفته TypeScript مانند انواع template literal و conditional را برای نوشتن کدی خواناتر و قابل نگهداریتر کاوش کنید. بر دستکاری انواع برای سناریوهای پیچیده مسلط شوید.
انواع پیشرفته TypeScript: تسلط بر انواع Template Literal و Conditional
قدرت TypeScript در سیستم نوع قدرتمند آن نهفته است. در حالی که انواع پایهای مانند string، number و boolean برای بسیاری از سناریوها کافی هستند، ویژگیهای پیشرفتهای مانند انواع template literal و conditional سطح جدیدی از خوانایی و ایمنی نوع را باز میکنند. این راهنما یک نمای کلی جامع از این انواع پیشرفته، کاوش در قابلیتهای آنها و نمایش کاربردهای عملی را ارائه میدهد.
درک انواع Template Literal
انواع Template literal بر اساس template literal های جاوااسکریپت ساخته شدهاند و به شما این امکان را میدهند که انواعی را بر اساس درونیابی رشته تعریف کنید. این ویژگی امکان ایجاد انواعی را فراهم میکند که الگوهای رشتهای خاصی را نمایش میدهند و کد شما را قویتر و قابل پیشبینیتر میکنند.
سینتکس و کاربرد پایه
انواع Template literal از بکتیک (`) برای دربرگرفتن تعریف نوع استفاده میکنند، مشابه template literal های جاوااسکریپت. درون بکتیکها، میتوانید انواع دیگر را با استفاده از سینتکس ${} درونیابی کنید. جادو اینجا اتفاق میافتد - شما در اصل در حال ایجاد نوعی هستید که یک رشته است و در زمان کامپایل بر اساس انواع داخل درونیابی ساخته میشود.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/${string}`;
// مثال کاربردی
const getEndpoint: APIEndpoint = "/api/users"; // معتبر
const postEndpoint: APIEndpoint = "/api/products/123"; // معتبر
const invalidEndpoint: APIEndpoint = "/admin/settings"; // TypeScript در اینجا خطایی نشان نمیدهد زیرا `string` میتواند هر چیزی باشد
در این مثال، APIEndpoint نوعی است که هر رشتهای را که با /api/ شروع میشود، نمایش میدهد. در حالی که این مثال پایه مفید است، قدرت واقعی انواع template literal زمانی آشکار میشود که با محدودیتهای نوعی خاصتری ترکیب شوند.
ترکیب با انواع Union
انواع Template literal واقعاً زمانی میدرخشند که با انواع union استفاده شوند. این به شما امکان میدهد انواعی را ایجاد کنید که مجموعهای خاص از ترکیبات رشتهای را نمایش میدهند.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIPath = "users" | "products" | "orders";
type APIEndpoint = `/${APIPath}/${HTTPMethod}`;
// نقاط پایانی API معتبر
const getUsers: APIEndpoint = "/users/GET";
const postProducts: APIEndpoint = "/products/POST";
// نقاط پایانی API نامعتبر (منجر به خطاهای TypeScript خواهد شد)
// const invalidEndpoint: APIEndpoint = "/users/PATCH"; // خطا: "/users/PATCH" به نوع "/users/GET" | "/users/POST" | "/users/PUT" | "/users/DELETE" | "/products/GET" | "/products/POST" | ... 3 مورد دیگر ... | "/orders/DELETE" قابل انتساب نیست.
اکنون، APIEndpoint یک نوع محدودتر است که فقط ترکیبات خاصی از مسیرهای API و متدهای HTTP را مجاز میداند. TypeScript هرگونه تلاش برای استفاده از ترکیبات نامعتبر را پرچمگذاری کرده و ایمنی نوع را افزایش میدهد.
دستکاری رشته با انواع Template Literal
TypeScript انواع ذاتی دستکاری رشته را ارائه میدهد که به طور یکپارچه با انواع template literal کار میکنند. این انواع به شما امکان میدهند رشتهها را در زمان کامپایل تغییر دهید.
- Uppercase: یک رشته را به حروف بزرگ تبدیل میکند.
- Lowercase: یک رشته را به حروف کوچک تبدیل میکند.
- Capitalize: حرف اول یک رشته را بزرگ میکند.
- Uncapitalize: حرف اول یک رشته را کوچک میکند.
type Greeting = "hello world";
type UppercaseGreeting = Uppercase; // "HELLO WORLD"
type LowercaseGreeting = Lowercase; // "hello world"
type CapitalizedGreeting = Capitalize; // "Hello world"
type UncapitalizedGreeting = Uncapitalize; // "hello world"
این انواع دستکاری رشته به ویژه برای تولید خودکار انواع بر اساس قراردادهای نامگذاری مفید هستند. به عنوان مثال، میتوانید انواع اکشن را از نام رویدادها استخراج کنید یا بالعکس.
کاربردهای عملی انواع Template Literal
- تعریف نقطه پایانی API: همانطور که در بالا نشان داده شد، تعریف نقاط پایانی API با محدودیتهای نوع دقیق.
- مدیریت رویداد: ایجاد انواع برای نام رویدادها با پیشوندها و پسوندهای خاص.
- تولید کلاس CSS: تولید نام کلاسهای CSS بر اساس نام و وضعیت کامپوننتها.
- ساخت کوئری پایگاه داده: تضمین ایمنی نوع هنگام ساخت کوئریهای پایگاه داده.
مثال بینالمللی: قالببندی ارز
تصور کنید در حال ساخت یک برنامه مالی هستید که از چندین ارز پشتیبانی میکند. میتوانید از انواع template literal برای اعمال قالببندی صحیح ارز استفاده کنید.
type CurrencyCode = "USD" | "EUR" | "GBP" | "JPY";
type CurrencyFormat = `${number} ${T}`;
const priceUSD: CurrencyFormat<"USD"> = "100 USD"; // معتبر
const priceEUR: CurrencyFormat<"EUR"> = "50 EUR"; // معتبر
// const priceInvalid: CurrencyFormat<"USD"> = "100 EUR"; // خطا: نوع 'string' به نوع '`${number} USD`' قابل انتساب نیست.
function formatCurrency(amount: number, currency: T): CurrencyFormat {
return `${amount} ${currency}`;
}
const formattedUSD = formatCurrency(250, "USD"); // نوع: "250 USD"
const formattedEUR = formatCurrency(100, "EUR"); // نوع: "100 EUR"
این مثال تضمین میکند که مقادیر ارزی همیشه با کد ارز صحیح قالببندی میشوند و از خطاهای احتمالی جلوگیری میکند.
ورود به دنیای انواع Conditional
انواع Conditional منطق انشعابی را به سیستم نوع TypeScript وارد میکنند و به شما این امکان را میدهند که انواعی را تعریف کنید که به انواع دیگر وابسته هستند. این ویژگی برای ایجاد تعاریف نوع بسیار انعطافپذیر و قابل استفاده مجدد فوقالعاده قدرتمند است.
سینتکس و کاربرد پایه
انواع Conditional از کلمه کلیدی infer و عملگر سهتایی (condition ? trueType : falseType) برای تعریف شرایط نوع استفاده میکنند.
type IsString = T extends string ? true : false;
type StringCheck = IsString; // type StringCheck = true
type NumberCheck = IsString; // type NumberCheck = false
در این مثال، IsString یک نوع شرطی است که بررسی میکند آیا T به string قابل انتساب است یا خیر. اگر باشد، نوع به true حل میشود؛ در غیر این صورت، به false حل میشود.
کلمه کلیدی infer
کلمه کلیدی infer به شما امکان میدهد یک نوع را از نوع دیگری استخراج کنید. این به ویژه هنگام کار با انواع پیچیده مانند انواع تابع یا انواع آرایه مفید است.
type ReturnType any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType; // type AddReturnType = number
در این مثال، ReturnType نوع بازگشتی یک نوع تابع T را استخراج میکند. بخش infer R از نوع شرطی، نوع بازگشتی را استنتاج کرده و آن را به متغیر نوع R اختصاص میدهد. اگر T یک نوع تابع نباشد، نوع به any حل میشود.
انواع Conditional توزیعی
انواع Conditional زمانی توزیعی میشوند که نوع بررسی شده یک پارامتر نوع «عریان» (naked) باشد. این بدان معناست که نوع شرطی به طور جداگانه برای هر عضو از نوع union اعمال میشود.
type ToArray = T extends any ? T[] : never;
type NumberOrStringArray = ToArray; // type NumberOrStringArray = string[] | number[]
در این مثال، ToArray یک نوع T را به یک نوع آرایه تبدیل میکند. از آنجایی که T یک پارامتر نوع عریان است (در نوع دیگری پیچیده نشده است)، نوع شرطی به طور جداگانه برای number و string اعمال میشود و در نتیجه یک union از number[] و string[] ایجاد میشود.
کاربردهای عملی انواع Conditional
- استخراج انواع بازگشتی: همانطور که در بالا نشان داده شد، استخراج نوع بازگشتی یک تابع.
- فیلتر کردن انواع از یک Union: ایجاد نوعی که فقط شامل انواع خاصی از یک union باشد.
- تعریف انواع تابع Overloaded: ایجاد انواع مختلف تابع بر اساس انواع ورودی.
- ایجاد Type Guards: تعریف توابعی که نوع یک متغیر را محدودتر میکنند.
مثال بینالمللی: مدیریت فرمتهای مختلف تاریخ
مناطق مختلف جهان از فرمتهای تاریخ متفاوتی استفاده میکنند. میتوانید از انواع شرطی برای مدیریت این تفاوتها استفاده کنید.
type DateFormat = "YYYY-MM-DD" | "MM/DD/YYYY" | "DD.MM.YYYY";
type ParseDate = T extends "YYYY-MM-DD"
? { year: number; month: number; day: number; format: "YYYY-MM-DD" }
: T extends "MM/DD/YYYY"
? { month: number; day: number; year: number; format: "MM/DD/YYYY" }
: T extends "DD.MM.YYYY"
? { day: number; month: number; year: number; format: "DD.MM.YYYY" }
: never;
function parseDate(dateString: string, format: T): ParseDate {
// (پیادهسازی فرمتهای مختلف تاریخ را مدیریت میکند)
if (format === "YYYY-MM-DD") {
const [year, month, day] = dateString.split("-").map(Number);
return { year, month, day, format } as ParseDate;
} else if (format === "MM/DD/YYYY") {
const [month, day, year] = dateString.split("/").map(Number);
return { month, day, year, format } as ParseDate;
} else if (format === "DD.MM.YYYY") {
const [day, month, year] = dateString.split(".").map(Number);
return { day, month, year, format } as ParseDate;
} else {
throw new Error("Invalid date format");
}
}
const parsedDateISO = parseDate("2023-10-27", "YYYY-MM-DD"); // Type: { year: number; month: number; day: number; format: "YYYY-MM-DD"; }
const parsedDateUS = parseDate("10/27/2023", "MM/DD/YYYY"); // Type: { month: number; day: number; year: number; format: "MM/DD/YYYY"; }
const parsedDateEU = parseDate("27.10.2023", "DD.MM.YYYY"); // Type: { day: number; month: number; year: number; format: "DD.MM.YYYY"; }
console.log(parsedDateISO.year); // به سال دسترسی پیدا میکنیم با علم به اینکه وجود خواهد داشت
این مثال از انواع شرطی برای تعریف توابع تجزیه تاریخ مختلف بر اساس فرمت تاریخ مشخص شده استفاده میکند. نوع ParseDate تضمین میکند که شیء بازگشتی دارای خصوصیات صحیح بر اساس فرمت باشد.
ترکیب انواع Template Literal و Conditional
قدرت واقعی زمانی آشکار میشود که شما انواع template literal و conditional را با هم ترکیب کنید. این امکان دستکاریهای نوع فوقالعاده قدرتمندی را فراهم میکند.
type EventName = `on${Capitalize}`;
type ExtractEventPayload = T extends EventName
? { type: T; payload: any } // برای نمایش سادهسازی شده است
: never;
type ClickEvent = EventName<"click">; // "onClick"
type MouseOverEvent = EventName<"mouseOver">; // "onMouseOver"
//مثال تابعی که یک نوع میگیرد
function processEvent(event: T): ExtractEventPayload {
//در یک پیادهسازی واقعی، ما واقعاً رویداد را ارسال میکنیم.
console.log(`Processing event ${event}`);
//در یک پیادهسازی واقعی، payload بر اساس نوع رویداد خواهد بود.
return { type: event, payload: {} } as ExtractEventPayload;
}
//توجه داشته باشید که انواع بازگشتی بسیار خاص هستند:
const clickEvent = processEvent("onClick"); // { type: "onClick"; payload: any; }
const mouseOverEvent = processEvent("onMouseOver"); // { type: "onMouseOver"; payload: any; }
//اگر از رشتههای دیگر استفاده کنید، never دریافت میکنید:
// const someOtherEvent = processEvent("someOtherEvent"); // نوع `never` است
بهترین شیوهها و ملاحظات
- ساده نگه دارید: در حالی که این انواع پیشرفته قدرتمند هستند، میتوانند به سرعت پیچیده شوند. برای وضوح و قابلیت نگهداری تلاش کنید.
- به طور کامل تست کنید: با نوشتن تستهای واحد جامع، اطمینان حاصل کنید که تعاریف نوع شما همانطور که انتظار میرود رفتار میکنند.
- کد خود را مستند کنید: هدف و رفتار انواع پیشرفته خود را به وضوح مستند کنید تا خوانایی کد را بهبود بخشید.
- عملکرد را در نظر بگیرید: استفاده بیش از حد از انواع پیشرفته میتواند بر زمان کامپایل تأثیر بگذارد. کد خود را پروفایل کرده و در صورت لزوم بهینهسازی کنید.
نتیجهگیری
انواع Template literal و conditional ابزارهای قدرتمندی در زرادخانه TypeScript هستند. با تسلط بر این انواع پیشرفته، میتوانید کدی خواناتر، قابل نگهداریتر و با ایمنی نوع بالاتر بنویسید. این ویژگیها به شما امکان میدهند روابط پیچیده بین انواع را ثبت کنید، محدودیتهای سختگیرانهتری را اعمال کنید و تعاریف نوع بسیار قابل استفاده مجدد ایجاد کنید. این تکنیکها را برای ارتقاء مهارتهای TypeScript خود و ساخت برنامههای قوی و مقیاسپذیر برای مخاطبان جهانی به کار بگیرید.